﻿// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动！任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任！


//using DocumentFormat.OpenXml.Spreadsheet;
using Elastic.Clients.Elasticsearch.Nodes;
using IES.NET.Application.Entity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using System.Drawing;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.SS.Util;
using System.IO;
namespace IES.NET.Application;



/// <summary>
/// 叠构审核服务
/// </summary>
[ApiDescriptionSettings(ApplicationConst.GroupName, Order = 100)]
public class StkreviewService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<Stkreview> _stkreviewRep;
    private readonly SqlSugarRepository<SysFile> _sysFileRep;
    private readonly IWebHostEnvironment _webHostEnvironment;
    public StkreviewService(SqlSugarRepository<Stkreview> stkreviewRep, SqlSugarRepository<SysFile> sysFileRep,
    IWebHostEnvironment webHostEnvironment = null)
    {
        _stkreviewRep = stkreviewRep;
        _sysFileRep = sysFileRep;
        _webHostEnvironment = webHostEnvironment;
    }

    /// <summary>
    /// 分页查询叠构审核
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [ApiDescriptionSettings(Name = "Page")]
    [DisplayName("分页查询叠构审核")]
    public async Task<SqlSugarPagedList<StkreviewOutput>> Page(PageStkreviewInput input)
    {
		input.SearchKey = input.SearchKey?.Trim();
        // 先查询每个job_name对应的最大revision_id,job_name相同的取revision_id最大的一行
        var maxRevisions = _stkreviewRep.AsQueryable()
            .Where(u => u.IsDelete == false)
            .GroupBy(u => new { u.job_name })
            .Select(g => new
            {
                g.job_name,
                MaxRevisionId = SqlFunc.AggregateMax(g.revision_id)
            });

        // 主查询
        var query = _stkreviewRep.AsQueryable()
            .InnerJoin(maxRevisions, (s, m) =>
                s.job_name == m.job_name &&
                s.revision_id == m.MaxRevisionId)
            .Where(s => s.IsDelete == false)
            .WhereIF(!string.IsNullOrEmpty(input.SearchKey), s =>
                s.job_name.Contains(input.SearchKey)
                || s.approve_notes.Contains(input.SearchKey)
                || s.mi_user.Contains(input.SearchKey)
                || s.approver.Contains(input.SearchKey)
                || s.submit_notes.Contains(input.SearchKey)
                || s.check_notes.Contains(input.SearchKey)
                || s.approve_state.Contains(input.SearchKey)
            )
            .WhereIF(!string.IsNullOrWhiteSpace(input.job_name), s => s.job_name.Contains(input.job_name.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.approve_state), s => s.approve_state == input.approve_state)
            .WhereIF(!string.IsNullOrWhiteSpace(input.approve_notes), s => s.approve_notes.Contains(input.approve_notes.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.mi_user), s => s.mi_user.Contains(input.mi_user.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.approver), s => s.approver.Contains(input.approver.Trim()))
            .WhereIF(input.submit_timeRange != null && input.submit_timeRange.Length == 2, s =>
                s.submit_time >= input.submit_timeRange[0] && s.submit_time <= input.submit_timeRange[1])
            .WhereIF(!string.IsNullOrWhiteSpace(input.submit_notes), s => s.submit_notes.Contains(input.submit_notes.Trim()))
            .WhereIF(input.approve_timeRange != null && input.approve_timeRange.Length == 2, s =>
                s.approve_time >= input.approve_timeRange[0] && s.approve_time <= input.approve_timeRange[1])
            .WhereIF(input.creation_dateRange != null && input.creation_dateRange.Length == 2, s =>
                s.creation_date >= input.creation_dateRange[0] && s.creation_date <= input.creation_dateRange[1])
            .WhereIF(!string.IsNullOrWhiteSpace(input.check_notes), s => s.check_notes.Contains(input.check_notes.Trim()))
            .Select<StkreviewOutput>().Distinct();
        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
        //var query = _stkreviewRep.AsQueryable()

        //    .WhereIF(!string.IsNullOrEmpty(input.SearchKey), u =>
        //        u.job_name.Contains(input.SearchKey)
        //        || u.approve_notes.Contains(input.SearchKey)
        //        || u.mi_user.Contains(input.SearchKey)
        //        || u.approver.Contains(input.SearchKey)
        //        || u.submit_notes.Contains(input.SearchKey)
        //        || u.check_notes.Contains(input.SearchKey)
        //        || u.approve_state.Contains(input.SearchKey)
        //    )
        //    .Where(u => u.IsDelete == false)
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.job_name), u => u.job_name.Contains(input.job_name.Trim()))
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.approve_state), u => u.approve_state == input.approve_state)
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.approve_notes), u => u.approve_notes.Contains(input.approve_notes.Trim()))
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.mi_user), u => u.mi_user.Contains(input.mi_user.Trim()))
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.approver), u => u.approver.Contains(input.approver.Trim()))
        //    .WhereIF(input.submit_timeRange != null && input.submit_timeRange.Length == 2, u => u.submit_time >= input.submit_timeRange[0] && u.submit_time <= input.submit_timeRange[1])
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.submit_notes), u => u.submit_notes.Contains(input.submit_notes.Trim()))
        //    .WhereIF(input.approve_timeRange != null && input.approve_timeRange.Length == 2, u => u.approve_time >= input.approve_timeRange[0] && u.approve_time <= input.approve_timeRange[1])
        //    .WhereIF(input.creation_dateRange != null && input.creation_dateRange.Length == 2, u => u.creation_date >= input.creation_dateRange[0] && u.creation_date <= input.creation_dateRange[1])
        //    .WhereIF(!string.IsNullOrWhiteSpace(input.check_notes), u => u.check_notes.Contains(input.check_notes.Trim()))
        //    .Select<StkreviewOutput>();
        //return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
    }

    /// <summary>
    /// 增加叠构审核
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [ApiDescriptionSettings(Name = "Add")]
    [DisplayName("增加叠构审核")]
    public async Task<long> Add(AddStkreviewInput input)
    {
        var entity = input.Adapt<Stkreview>();
        await _stkreviewRep.InsertAsync(entity);
        return entity.Id;
    }

    /// <summary>
    /// 删除叠构审核
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [ApiDescriptionSettings(Name = "Delete")]
    [DisplayName("删除叠构审核")]
    public async Task Delete(DeleteStkreviewInput input)
    {
        // 构建删除条件
        var query = _stkreviewRep.AsDeleteable();

        if (!string.IsNullOrEmpty(input.job_name))
        {
            // 否则使用 job_name 和 AppSource 作为条件
            query = query.Where(u => u.job_name == input.job_name);
            await query.ExecuteCommandAsync();
        }
        
        else
        {
            throw Oops.Oh("后端返回错误: 删除条件不足：需要提供 job_name");
            
        }

        
        // 执行假删除
        //foreach (var entity in entities)
        //{
        //    //await _stkreviewRep.FakeDeleteAsync(entity);   //假删除
        //    await _stkreviewRep.DeleteByIdAsync(entity.Id);   //真删除
        //}
        //await _stkreviewRep.FakeDeleteAsync(entity);   //假删除
        //await _stkreviewRep.DeleteAsync(entity);   //真删除
    }

    private string attachFileRootPath = "\\\\192.168.245.79\\InplanData\\IES\\files\\stackup_review";
    /// <summary>
    /// 获取指定料号的审批历史记录
    /// </summary>
    /// <param name="jobName">料号名</param>
    /// <returns>审批历史记录列表</returns>
    [HttpGet]
    [ApiDescriptionSettings(Name = "GetApprovalHistoryList")]
    [DisplayName("获取审批历史记录")]
    public async Task<List<StkreviewOutput>> GetApprovalHistoryList([FromQuery] string jobName)
    {
        var query = _stkreviewRep.AsQueryable()
            .Where(u => u.IsDelete == false && u.job_name == jobName)
            .OrderByDescending(u => u.submit_time)
            .Select<StkreviewOutput>();
        List <StkreviewOutput> result = await query.ToListAsync();

        

        foreach (StkreviewOutput item in result)
        {
            var latestImageFiles = await _sysFileRep.AsQueryable()
        .Where(f => f.AppSource == "叠层审核"
        && f.Category == "stackup_image"
                 && f.RelationId == item.Id)
        .OrderByDescending(f => f.CreateTime) // 按创建时间倒序
        .ToListAsync();

            // 创建ID到最新文件URL的映射字典，只保留每个RelationId的第一条记录（最新的）
            var imageUrlMap = latestImageFiles
                .GroupBy(f => f.RelationId)
                .ToDictionary(
                    g => g.Key,
                    g => g.First().Url // 由于之前已经按CreateTime排序，First()会得到最新的记录
                );


            string imagePath = $"{attachFileRootPath}/{item.job_name}/{item.revision_id}/{item.job_name}_stackupImage_{item.revision_id}.png";
            imagePath = imageUrlMap[item.Id];
            Console.WriteLine("imagePath:" + imagePath);
            item.stackup_image = imagePath;
            // 获取wwwroot的物理路径
            string webRootPath = _webHostEnvironment.WebRootPath;
            // 组合完整的物理路径
            imagePath = Path.Combine(webRootPath, imagePath.TrimStart('/'));
            if (File.Exists(imagePath))
            {
                // 读取图片文件
                byte[] imageBytes = System.IO.File.ReadAllBytes(imagePath);

                // 转换为base64字符串
                string base64String = Convert.ToBase64String(imageBytes);

                // 添加数据URI scheme前缀
                string imageUrl = $"data:image/png;base64,{base64String}";
                item.stackup_image = imageUrl;
            }
            item.check_notes = item.check_notes.Replace("<Br>", Environment.NewLine).Replace("<br>", Environment.NewLine);
        }

        return result;
    }

    /// <summary>
    /// 更新叠构审核
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [ApiDescriptionSettings(Name = "Update")]
    [DisplayName("更新叠构审核")]
    public async Task Update(UpdateStkreviewInput input)
    {
        if (input.Id == 0)
        {
            throw Oops.Oh("缺少必要的更新条件：记录ID");
        }

        // 转换审批状态
        if (input.approve_state == "OK" || input.approve_state == "已批准")
        {
            input.approve_state = "已批准";
        }

        else
        {
            input.approve_state = "待修改";
        }

        // 设置审批时间
        input.approve_time = DateTime.Now;

        // 只更新审核相关字段
        var result = await _stkreviewRep.AsUpdateable()
            .SetColumns(x => new Stkreview
            {
                approve_state = input.approve_state,
                approve_time = input.approve_time,
                approve_notes = input.approve_notes,
                approver = input.approver
            })
            .Where(x => x.Id == input.Id)
            .ExecuteCommandAsync();

        if (result <= 0)
        {
            throw Oops.Oh("更新失败：未找到匹配的记录");
        }
    }

    /// <summary>
    /// 获取叠构审核
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpGet]
    [ApiDescriptionSettings(Name = "Detail")]
    [DisplayName("获取叠构审核")]
    public async Task<Stkreview> Detail([FromQuery] QueryByIdStkreviewInput input)
    {
        var result = await _stkreviewRep.GetFirstAsync(u => u.Id == input.Id);
        
        //string stackupImageName = result.stackup_image;
        string imagePath = $"{attachFileRootPath}/{result.job_name}/{result.revision_id}/{result.job_name}_stackupImage_{result.revision_id}.png";

        var latestImageFiles = await _sysFileRep.AsQueryable()
        .Where(f => f.AppSource == "叠层审核"
        && f.Category == "stackup_image"
                 && f.RelationId == input.Id)
        .OrderByDescending(f => f.CreateTime) // 按创建时间倒序
        .ToListAsync();

        // 创建ID到最新文件URL的映射字典，只保留每个RelationId的第一条记录（最新的）
        var imageUrlMap = latestImageFiles
            .GroupBy(f => f.RelationId)
            .ToDictionary(
                g => g.Key,
                g => g.First().Url // 由于之前已经按CreateTime排序，First()会得到最新的记录
            );

        imagePath = imageUrlMap[input.Id];

        Console.WriteLine(imagePath);

        Image image = Image.FromFile(imagePath);
        using (MemoryStream ms = new MemoryStream())
        {
            // 将图片保存到内存流中
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            // 将内存流转换为字节数组
            byte[] imageBytes = ms.ToArray();
            // 将字节数组转换为Base64字符串
            result.stackup_image = Convert.ToBase64String(imageBytes);
            Console.WriteLine(result.stackup_image);
        }
        Console.WriteLine(result);
        return result ?? throw Oops.Oh(ErrorCodeEnum.D1002);
        //return await _stkreviewRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
    }

    /// <summary>
    /// 获取叠构审核列表
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpGet]
    [ApiDescriptionSettings(Name = "List")]
    [DisplayName("获取叠构审核列表")]
    public async Task<List<StkreviewOutput>> List([FromQuery] PageStkreviewInput input)
    {
        return await _stkreviewRep.AsQueryable()
            
            .Select<StkreviewOutput>().ToListAsync();
    }

    // 修改 GetAttachments 方法以支持不同类型的附件
    [HttpGet]
    [ApiDescriptionSettings(Name = "GetAttachments")]
    [DisplayName("获取附件列表")]
    public async Task<List<FileInfo>> GetAttachments([FromQuery] string jobName, [FromQuery] string revisionId, [FromQuery] string type)
    {
        var attachmentPath = Path.Combine(attachFileRootPath, jobName, revisionId, type); // type 可以是 "submit" 或 "approve"
        var result = new List<FileInfo>();

        if (Directory.Exists(attachmentPath))
        {
            var directory = new DirectoryInfo(attachmentPath);
            var files = directory.GetFiles();

            foreach (var file in files)
            {
                result.Add(new FileInfo
                {
                    FileName = file.Name,
                    FileSize = file.Length,
                    CreateTime = file.CreationTime,
                    FilePath = Path.Combine(jobName, revisionId, type, file.Name)
                });
            }
        }

        return result;
    }

    [HttpPost]
    [ApiDescriptionSettings(Name = "UploadAttachment")]
    [DisplayName("上传附件")]
    public async Task<string> UploadAttachment([FromForm] AttachmentUploadInput input)
    {
        if (input.File == null || input.File.Length == 0)
            throw Oops.Oh("请选择要上传的文件");

        var uploadPath = Path.Combine(attachFileRootPath, input.JobName, input.RevisionId, input.Type); // 添加 type 参数

        // 确保目录存在
        if (!Directory.Exists(uploadPath))
            Directory.CreateDirectory(uploadPath);

        var fileName = input.File.FileName;
        var filePath = Path.Combine(uploadPath, fileName);

        // 如果文件已存在，添加时间戳
        if (File.Exists(filePath))
        {
            var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
            var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
            var fileExt = Path.GetExtension(fileName);
            fileName = $"{fileNameWithoutExt}_{timestamp}{fileExt}";
            filePath = Path.Combine(uploadPath, fileName);
        }

        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await input.File.CopyToAsync(stream);
        }

        return fileName;
    }

    [HttpPost]
    [ApiDescriptionSettings(Name = "DeleteAttachment")]
    [DisplayName("删除附件")]
    public async Task DeleteAttachment(AttachmentDeleteInput input)
    {
        var filePath = Path.Combine(attachFileRootPath, input.JobName, input.RevisionId, input.Type, input.FileName);

        if (File.Exists(filePath))
        {
            File.Delete(filePath);
        }
        else
        {
            throw Oops.Oh("文件不存在");
        }
    }

   
    [HttpGet]
    [ApiDescriptionSettings(Name = "DownloadFile")]
    [DisplayName("下载文件")]
    public async Task<FileContentResult> DownloadFile([FromQuery] string path)
    {
        var filePath = Path.Combine(attachFileRootPath, path);
        if (!System.IO.File.Exists(filePath))
        {
            // 如果文件不存在，抛出异常
            throw Oops.Oh("文件不存在");
        }

        try
        {
            var fileName = Path.GetFileName(filePath);
            var fileBytes = await System.IO.File.ReadAllBytesAsync(filePath);

            // 设置响应头
            App.HttpContext.Response.Headers.Add("Content-Disposition", $"attachment; filename={Uri.EscapeDataString(fileName)}");
            return new FileContentResult(fileBytes, "application/octet-stream");
        }
        catch (Exception ex)
        {
            throw Oops.Oh($"下载失败: {ex.Message}");
        }
    }

    /// <summary>
    /// 导出叠构审核数据到Excel
    /// </summary>
    /// <returns>文件流</returns>
    [HttpGet]
    [ApiDescriptionSettings(Name = "ExportToExcel")]
    [DisplayName("导出叠构审核数据")]
    public async Task<IActionResult> ExportToExcel()
    {
        // 查询所有数据，按料号和版本ID排序
        var data = await _stkreviewRep.AsQueryable()
            .Where(u => u.IsDelete == false)
            .OrderBy(u => u.job_name, OrderByType.Asc)
            .OrderBy(u => u.revision_id, OrderByType.Asc)
            .ToListAsync();

        using var ms = new MemoryStream();
        IWorkbook workbook = new XSSFWorkbook();

        // 创建工作表 - 使用 IWorkbook 接口的方法
        ISheet sheet = workbook.CreateSheet();
        // 设置工作表名称
        workbook.SetSheetName(0, "叠构审核数据");

        // 创建标题行
        var headerRow = sheet.CreateRow(0);
        string[] headers = new string[]
        {
        "料号名", "版本号", "审核状态", "审批备注", "MI制作",
        "审核者", "提交时间", "制作者备注", "审核时间", "防呆检查结果"
        };

        // 设置标题样式
        ICellStyle headerStyle = workbook.CreateCellStyle();
        IFont headerFont = workbook.CreateFont();
        headerFont.Boldweight = (short)FontBoldWeight.Bold;
        headerStyle.SetFont(headerFont);
        headerStyle.FillForegroundColor = IndexedColors.Grey25Percent.Index;
        headerStyle.FillPattern = FillPattern.SolidForeground;
        headerStyle.BorderTop = BorderStyle.Thin;
        headerStyle.BorderBottom = BorderStyle.Thin;
        headerStyle.BorderLeft = BorderStyle.Thin;
        headerStyle.BorderRight = BorderStyle.Thin;
        headerStyle.Alignment = HorizontalAlignment.Center;
        headerStyle.VerticalAlignment = VerticalAlignment.Center;

        // 写入标题
        for (int i = 0; i < headers.Length; i++)
        {
            var cell = headerRow.CreateCell(i);
            cell.SetCellValue(headers[i]);
            cell.CellStyle = headerStyle;
        }

        // 创建日期样式
        ICellStyle dateStyle = workbook.CreateCellStyle();
        IDataFormat dataFormat = workbook.CreateDataFormat();
        dateStyle.DataFormat = dataFormat.GetFormat("yyyy-mm-dd hh:mm:ss");
        dateStyle.BorderTop = BorderStyle.Thin;
        dateStyle.BorderBottom = BorderStyle.Thin;
        dateStyle.BorderLeft = BorderStyle.Thin;
        dateStyle.BorderRight = BorderStyle.Thin;
        dateStyle.Alignment = HorizontalAlignment.Center;

        // 创建普通单元格样式
        ICellStyle normalStyle = workbook.CreateCellStyle();
        normalStyle.BorderTop = BorderStyle.Thin;
        normalStyle.BorderBottom = BorderStyle.Thin;
        normalStyle.BorderLeft = BorderStyle.Thin;
        normalStyle.BorderRight = BorderStyle.Thin;
        normalStyle.WrapText = false;  // 允许文本自动换行

        // 创建居中对齐的样式（用于料号、版本号等需要居中的列）
        ICellStyle centerStyle = workbook.CreateCellStyle();
        centerStyle.CloneStyleFrom(normalStyle);
        centerStyle.Alignment = HorizontalAlignment.Center;
        centerStyle.VerticalAlignment = VerticalAlignment.Center;

        // 写入数据行
        int rowIndex = 1;
        string currentJobName = null;

        foreach (var item in data)
        {
            var row = sheet.CreateRow(rowIndex++);
            bool isNewJobName = currentJobName != item.job_name;
            if (isNewJobName)
            {
                currentJobName = item.job_name;
            }

            SetCellValue(row, 0, item.job_name, isNewJobName ? centerStyle : null);
            SetCellValue(row, 1, item.revision_id, isNewJobName ? centerStyle : null);
            SetCellValue(row, 2, item.approve_state, isNewJobName ? centerStyle : null);
            //SetCellValue(row, 3, item.approve_notes, isNewJobName ? centerStyle : null);
            SetCellValue(row, 4, item.mi_user, isNewJobName ? centerStyle : null);
            SetCellValue(row, 5, item.approver, isNewJobName ? centerStyle : null);
            //SetCellValue(row, 6, item.submit_time, dateStyle);
            //SetCellValue(row, 7, item.submit_notes, isNewJobName ? centerStyle : null);

            // 使用日期样式的列
            if (item.submit_time.HasValue)
                SetCellValue(row, 6, item.submit_time.Value, dateStyle);
            if (item.approve_time.HasValue)
                SetCellValue(row, 8, item.approve_time.Value, dateStyle);

            // 使用普通样式的列（允许换行的长文本）
            SetCellValue(row, 3, item.approve_notes, normalStyle);
            SetCellValue(row, 7, item.submit_notes, normalStyle);
            SetCellValue(row, 9, item.check_notes?.Replace("<Br>", "\n").Replace("<br>", "\n"), normalStyle);
        }

        // 调整列宽
        int[] columnWidths = new int[] {
        20 * 256,  // 料号名
        10 * 256,  // 版本号
        8 * 256,  // 审核状态
        40 * 256,  // 审批备注
        7 * 256,  // MI制作
        7 * 256,  // 审核者
        20 * 256,  // 提交时间
        40 * 256,  // 制作者备注
        20 * 256,  // 审核时间
        40 * 256   // 防呆检查结果
    };

        // 设置列宽
        for (int i = 0; i < columnWidths.Length; i++)
        {
            sheet.SetColumnWidth(i, columnWidths[i]);
        }

        // 冻结首行
        sheet.CreateFreezePane(0, 1);

        // 写入文件流
        workbook.Write(ms);
        var bytes = ms.ToArray();

        string fileName = $"叠构审核数据_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
        return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        {
            FileDownloadName = fileName
        };
    }

    private void SetCellValue(IRow row, int column, object value, ICellStyle style = null)
    {
        var cell = row.CreateCell(column);

        if (value != null)
        {
            switch (value)
            {
                case string s:
                    cell.SetCellValue(s);
                    break;
                case DateTime dt:
                    cell.SetCellValue(dt);
                    break;
                case double d:
                    cell.SetCellValue(d);
                    break;
                case int i:
                    cell.SetCellValue(i);
                    break;
                case bool b:
                    cell.SetCellValue(b);
                    break;
                default:
                    cell.SetCellValue(value.ToString());
                    break;
            }
        }
        else
        {
            cell.SetCellValue("");
        }

        if (style != null)
        {
            cell.CellStyle = style;
        }
    }
    public class FileInfo
    {
        public string FileName { get; set; }
        public long FileSize { get; set; }
        public DateTime CreateTime { get; set; }
        public string FilePath { get; set; }
    }

    // 更新输入模型
    public class AttachmentUploadInput
    {
        [Required]
        public IFormFile File { get; set; }

        [Required]
        public string JobName { get; set; }

        [Required]
        public string RevisionId { get; set; }

        [Required]
        public string Type { get; set; } // "submit" 或 "approve"
    }

    public class AttachmentDeleteInput
    {
        [Required]
        public string JobName { get; set; }

        [Required]
        public string RevisionId { get; set; }

        [Required]
        public string FileName { get; set; }

        [Required]
        public string Type { get; set; } // "submit" 或 "approve"
    }

}
